#include <windows.h>
#include <stdio.h>
#include <jni.h>
#include "console_Console.h"

HANDLE hStdin, hStdout;
CONSOLE_SCREEN_BUFFER_INFO *info;

/*
 * Class:     console_Console
 * Method:    nativeInit
 * Signature: ()V
 */
JNIEXPORT jboolean JNICALL Java_console_Console_nativeInit(JNIEnv *env, jclass clazz) {
    DWORD fdwMode;

    hStdin = GetStdHandle(STD_INPUT_HANDLE); 
    if (hStdin == INVALID_HANDLE_VALUE) 
        return FALSE;

    hStdout = GetStdHandle(STD_OUTPUT_HANDLE); 
    if (hStdout == INVALID_HANDLE_VALUE) 
        return FALSE;

    fdwMode = ENABLE_WRAP_AT_EOL_OUTPUT | ENABLE_PROCESSED_INPUT; 
    if (! SetConsoleMode(hStdout, fdwMode) ) 
        return FALSE;
    
    info = (CONSOLE_SCREEN_BUFFER_INFO *)malloc(sizeof(CONSOLE_SCREEN_BUFFER_INFO));
    if (!GetConsoleScreenBufferInfo(hStdout, info)) {
        info = NULL;
    }

    return TRUE;
}

/*
 * Class:     console_Console
 * Method:    nativeReadChar
 * Signature: ()[I
 */
JNIEXPORT jintArray JNICALL Java_console_Console_nativeReadChar(JNIEnv *env, jclass clazz) {
    INPUT_RECORD irInBuf[1];
    int ret[4];
    int cNumRead;
    jintArray retArray;
    
    while (TRUE) {
        if (! ReadConsoleInput(hStdin, irInBuf, 1, &cNumRead) ) {
            return (*env)->NewIntArray(env, 0);
        }
        if (cNumRead == 1) {
            if (irInBuf[0].EventType == KEY_EVENT && irInBuf[0].Event.KeyEvent.bKeyDown == TRUE) {
                ret[0] = irInBuf[0].Event.KeyEvent.wVirtualKeyCode;
                ret[1] = irInBuf[0].Event.KeyEvent.wVirtualScanCode;
                ret[2] = irInBuf[0].Event.KeyEvent.uChar.AsciiChar;
                ret[3] = irInBuf[0].Event.KeyEvent.dwControlKeyState;
                retArray = (*env)->NewIntArray(env, 4);
                (*env)->SetIntArrayRegion(env, retArray, 0, 4, ret);
                return retArray;
            }
        }
    }        
    
}

/*
 * Class:     console_Console
 * Method:    nativeOutputChars
 * Signature: ([CII)V
 */
JNIEXPORT void JNICALL Java_console_Console_nativeOutputChars(JNIEnv *env, jclass clazz, jstring str) {
    const char *_str;
    jboolean iscopy;
    int nWritten, len, nleft;

    _str = (*env)->GetStringUTFChars(env, str, &iscopy);
    len = strlen(_str);
    nleft = len;
    while (nleft > 0) {
        WriteConsole(hStdout, _str + (len - nleft), nleft, &nWritten, NULL);
        nleft -= nWritten;
    }
    /*if (!WriteConsole(hStdout, _str, len, &nWritten, NULL)) {
        printf("WriteConsole failed %d", GetLastError());
    }*/
    if (iscopy) {
        (*env)->ReleaseStringUTFChars(env, str, _str);
    }
}

/*
 * Class:     console_Console
 * Method:    nativeGetCursorLocation
 * Signature: ()[I
 */
JNIEXPORT jintArray JNICALL Java_console_Console_nativeGetCursorLocation(JNIEnv *env, jclass clazz) {
    CONSOLE_SCREEN_BUFFER_INFO ci;
    jintArray retArray;
    int ret[2];
    if (!GetConsoleScreenBufferInfo(hStdout, &ci)) {
        printf("Error GetConsoleScreenBufferInfo %d", GetLastError());
        return (*env)->NewIntArray(env, 0);
    }
    ret[0] = ci.dwCursorPosition.X;
    ret[1] = ci.dwCursorPosition.Y;
    retArray = (*env)->NewIntArray(env, 2);
    (*env)->SetIntArrayRegion(env, retArray, 0, 2, ret);
    return retArray;
}

/*
 * Class:     console_Console
 * Method:    nativeSetCursorLocation
 * Signature: (II)Z
 */
JNIEXPORT jboolean JNICALL Java_console_Console_nativeSetCursorLocation(JNIEnv *env, jclass clazz, jint x, jint y) {
    COORD c;
    CONSOLE_SCREEN_BUFFER_INFO ci;
    c.X = x;
    c.Y = y;

    if (!SetConsoleCursorPosition(hStdout, c)) {
        return 0;
    }
    return 1;
}

/*
 * Class:     console_Console
 * Method:    nativeGetScreenSize
 * Signature: ()[I
 */
JNIEXPORT jintArray JNICALL Java_console_Console_nativeGetScreenSize(JNIEnv *env, jclass clazz) {
    int ret[4];
    jintArray retArray;
    if (&info == NULL)
        return (*env)->NewIntArray(env, 0);
    ret[0] = info->srWindow.Left;
    ret[1] = info->srWindow.Top;
    ret[2] = info->srWindow.Right;
    ret[3] = info->srWindow.Bottom;
    retArray = (*env)->NewIntArray(env, 4);
    (*env)->SetIntArrayRegion(env, retArray, 0, 4, ret);
    return retArray;
}
